/*
 * Copyright (C) 2019 The Dagger Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package dagger.hilt.processor.internal;

import static com.google.auto.common.MoreElements.asPackage;
import static com.google.auto.common.MoreElements.asVariable;
import static com.google.common.base.Preconditions.checkNotNull;
import static dagger.internal.codegen.extension.DaggerCollectors.toOptional;
import static javax.lang.model.element.Modifier.ABSTRACT;
import static javax.lang.model.element.Modifier.STATIC;

import com.google.auto.common.AnnotationMirrors;
import com.google.auto.common.GeneratedAnnotations;
import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
import com.google.common.base.CaseFormat;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.SetMultimap;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import dagger.internal.codegen.extension.DaggerStreams;
import dagger.internal.codegen.kotlin.KotlinMetadataUtil;
import java.lang.annotation.Annotation;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.inject.Qualifier;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ErrorType;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.SimpleAnnotationValueVisitor7;
import javax.lang.model.util.SimpleTypeVisitor7;

/** Static helper methods for writing a processor. */
public final class Processors {

  public static final String CONSTRUCTOR_NAME = "<init>";

  public static final String STATIC_INITIALIZER_NAME = "<clinit>";

  private static final String JAVA_CLASS = "java.lang.Class";

  /** Returns a map from {@link AnnotationMirror} attribute name to {@link AnnotationValue}s */
  public static ImmutableMap<String, AnnotationValue> getAnnotationValues(Elements elements,
      AnnotationMirror annotation) {
    ImmutableMap.Builder<String, AnnotationValue> annotationMembers = ImmutableMap.builder();
    for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> e
        : elements.getElementValuesWithDefaults(annotation).entrySet()) {
      annotationMembers.put(e.getKey().getSimpleName().toString(), e.getValue());
    }
    return annotationMembers.build();
  }

  /**
   * Returns a multimap from attribute name to the values that are an array of annotation mirrors.
   * The returned map will not contain mappings for any attributes that are not Annotation Arrays.
   *
   * <p>e.g. if the input was the annotation mirror for
   * <pre>
   *   {@literal @}Foo({{@literal @}Bar("hello"), {@literal @}Bar("world")})
   * </pre>
   * the map returned would have "value" map to a set containing the two @Bar annotation mirrors.
   */
  public static Multimap<String, AnnotationMirror> getAnnotationAnnotationArrayValues(
      Elements elements, AnnotationMirror annotation) {
    SetMultimap<String, AnnotationMirror> annotationMembers = LinkedHashMultimap.create();
    for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> e
        : elements.getElementValuesWithDefaults(annotation).entrySet()) {
      String attribute = e.getKey().getSimpleName().toString();
      Set<AnnotationMirror> annotationMirrors = new LinkedHashSet<>();
      e.getValue().accept(new AnnotationMirrorAnnotationValueVisitor(), annotationMirrors);
      annotationMembers.putAll(attribute, annotationMirrors);
    }
    return annotationMembers;
  }

  private static final class AnnotationMirrorAnnotationValueVisitor
      extends SimpleAnnotationValueVisitor7<Void, Set<AnnotationMirror>> {

    @Override
    public Void visitArray(List<? extends AnnotationValue> vals, Set<AnnotationMirror> types) {
      for (AnnotationValue val : vals) {
        val.accept(this, types);
      }
      return null;
    }

    @Override
    public Void visitAnnotation(AnnotationMirror a, Set<AnnotationMirror> annotationMirrors) {
      annotationMirrors.add(a);
      return null;
    }
  }

  /** Returns the {@link TypeElement} for a class attribute on an annotation. */
  public static TypeElement getAnnotationClassValue(
      Elements elements, AnnotationMirror annotation, String key) {
    return Iterables.getOnlyElement(getAnnotationClassValues(elements, annotation, key));
  }

  /** Returns a list of {@link TypeElement}s for a class attribute on an annotation. */
  public static ImmutableList<TypeElement> getAnnotationClassValues(
      Elements elements, AnnotationMirror annotation, String key) {
    ImmutableList<TypeElement> values = getOptionalAnnotationClassValues(elements, annotation, key);

    ProcessorErrors.checkState(
        values.size() >= 1,
        // TODO(b/152801981): Point to the annotation value rather than the annotated element.
        annotation.getAnnotationType().asElement(),
        "@%s, '%s' class is invalid or missing: %s",
        annotation.getAnnotationType().asElement().getSimpleName(),
        key,
        annotation);

    return values;
  }

  /** Returns a multimap from attribute name to elements for class valued attributes. */
  private static Multimap<String, DeclaredType> getAnnotationClassValues(
      Elements elements, AnnotationMirror annotation) {
    Element javaClass = elements.getTypeElement(JAVA_CLASS);
    SetMultimap<String, DeclaredType> annotationMembers = LinkedHashMultimap.create();
    for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> e :
        elements.getElementValuesWithDefaults(annotation).entrySet()) {
      Optional<DeclaredType> returnType = getOptionalDeclaredType(e.getKey().getReturnType());
      if (returnType.isPresent() && returnType.get().asElement().equals(javaClass)) {
        String attribute = e.getKey().getSimpleName().toString();
        Set<DeclaredType> declaredTypes = new LinkedHashSet<DeclaredType>();
        e.getValue().accept(new DeclaredTypeAnnotationValueVisitor(), declaredTypes);
        annotationMembers.putAll(attribute, declaredTypes);
      }
    }
    return annotationMembers;
  }

  /** Returns an optional {@link TypeElement} for a class attribute on an annotation. */
  public static Optional<TypeElement> getOptionalAnnotationClassValue(
      Elements elements, AnnotationMirror annotation, String key) {
    return getAnnotationClassValues(elements, annotation).get(key).stream()
        .map(MoreTypes::asTypeElement)
        .collect(toOptional());
  }

  /** Returns a list of {@link TypeElement}s for a class attribute on an annotation. */
  public static ImmutableList<TypeElement> getOptionalAnnotationClassValues(
      Elements elements, AnnotationMirror annotation, String key) {
    return ImmutableList.copyOf(
        getAnnotationClassValues(elements, annotation).get(key).stream()
            .map(MoreTypes::asTypeElement)
            .collect(Collectors.toList()));
  }

  private static final class DeclaredTypeAnnotationValueVisitor
      extends SimpleAnnotationValueVisitor7<Void, Set<DeclaredType>> {

    @Override public Void visitArray(
        List<? extends AnnotationValue> vals, Set<DeclaredType> types) {
      for (AnnotationValue val : vals) {
        val.accept(this, types);
      }
      return null;
    }

    @Override public Void visitType(TypeMirror t, Set<DeclaredType> types) {
      DeclaredType declared = MoreTypes.asDeclared(t);
      checkNotNull(declared);
      types.add(declared);
      return null;
    }
  }

  /**
   * If the received mirror represents a primitive type or an array of primitive types, this returns
   * the represented primitive type. Otherwise throws an IllegalStateException.
   */
  public static PrimitiveType getPrimitiveType(TypeMirror type) {
    return type.accept(
        new SimpleTypeVisitor7<PrimitiveType, Void> () {
          @Override public PrimitiveType visitArray(ArrayType type, Void unused) {
            return getPrimitiveType(type.getComponentType());
          }

          @Override public PrimitiveType visitPrimitive(PrimitiveType type, Void unused) {
            return type;
          }

          @Override public PrimitiveType defaultAction(TypeMirror type, Void unused) {
            throw new IllegalStateException("Unhandled type: " + type);
          }
        }, null /* the Void accumulator */);
  }

  /**
   * Returns an {@link Optional#of} the declared type if the received mirror represents a declared
   * type or an array of declared types, otherwise returns {@link Optional#empty}.
   */
  public static Optional<DeclaredType> getOptionalDeclaredType(TypeMirror type) {
    return Optional.ofNullable(
        type.accept(
            new SimpleTypeVisitor7<DeclaredType, Void>(null /* defaultValue */) {
              @Override
              public DeclaredType visitArray(ArrayType type, Void unused) {
                return MoreTypes.asDeclared(type.getComponentType());
              }

              @Override
              public DeclaredType visitDeclared(DeclaredType type, Void unused) {
                return type;
              }

              @Override
              public DeclaredType visitError(ErrorType type, Void unused) {
                return type;
              }
            },
            null /* the Void accumulator */));
  }

  /**
   * Returns the declared type if the received mirror represents a declared type or an array of
   * declared types, otherwise throws an {@link IllegalStateException}.
   */
  public static DeclaredType getDeclaredType(TypeMirror type) {
    return getOptionalDeclaredType(type)
        .orElseThrow(() -> new IllegalStateException("Not a declared type: " + type));
  }

  /** Gets the values from an annotation value representing a string array. */
  public static ImmutableList<String> getStringArrayAnnotationValue(AnnotationValue value) {
    return value.accept(new SimpleAnnotationValueVisitor7<ImmutableList<String>, Void>() {
      @Override
      public ImmutableList<String> defaultAction(Object o, Void unused) {
        throw new IllegalStateException("Expected an array, got instead: " + o);
      }

      @Override
      public ImmutableList<String> visitArray(List<? extends AnnotationValue> values,
          Void unused) {
        ImmutableList.Builder<String> builder = ImmutableList.builder();
        for (AnnotationValue value : values) {
          builder.add(getStringAnnotationValue(value));
        }
        return builder.build();
      }
    }, /* unused accumulator */ null);
  }

  /** Gets the values from an annotation value representing an int. */
  public static Boolean getBooleanAnnotationValue(AnnotationValue value) {
    return value.accept(
        new SimpleAnnotationValueVisitor7<Boolean, Void>() {
          @Override
          public Boolean defaultAction(Object o, Void unused) {
            throw new IllegalStateException("Expected a boolean, got instead: " + o);
          }

          @Override
          public Boolean visitBoolean(boolean value, Void unused) {
            return value;
          }
        }, /* unused accumulator */
        null);
  }

  /** Gets the values from an annotation value representing an int. */
  public static Integer getIntAnnotationValue(AnnotationValue value) {
    return value.accept(new SimpleAnnotationValueVisitor7<Integer, Void>() {
      @Override
      public Integer defaultAction(Object o, Void unused) {
        throw new IllegalStateException("Expected an int, got instead: " + o);
      }

      @Override
      public Integer visitInt(int value, Void unused) {
        return value;
      }
    }, /* unused accumulator */ null);
  }

  /** Gets the values from an annotation value representing a long. */
  public static Long getLongAnnotationValue(AnnotationValue value) {
    return value.accept(
        new SimpleAnnotationValueVisitor7<Long, Void>() {
          @Override
          public Long defaultAction(Object o, Void unused) {
            throw new IllegalStateException("Expected an int, got instead: " + o);
          }

          @Override
          public Long visitLong(long value, Void unused) {
            return value;
          }
        },
        null /* unused accumulator */);
  }

  /** Gets the values from an annotation value representing a string. */
  public static String getStringAnnotationValue(AnnotationValue value) {
    return value.accept(new SimpleAnnotationValueVisitor7<String, Void>() {
      @Override
      public String defaultAction(Object o, Void unused) {
        throw new IllegalStateException("Expected a string, got instead: " + o);
      }

      @Override
      public String visitString(String value, Void unused) {
        return value;
      }
    }, /* unused accumulator */ null);
  }

  /** Gets the values from an annotation value representing a DeclaredType. */
  public static DeclaredType getDeclaredTypeAnnotationValue(AnnotationValue value) {
    return value.accept(
        new SimpleAnnotationValueVisitor7<DeclaredType, Void>() {
          @Override
          public DeclaredType defaultAction(Object o, Void unused) {
            throw new IllegalStateException("Expected a TypeMirror, got instead: " + o);
          }

          @Override
          public DeclaredType visitType(TypeMirror typeMirror, Void unused) {
            return MoreTypes.asDeclared(typeMirror);
          }
        }, /* unused accumulator */
        null);
  }

  private static final SimpleAnnotationValueVisitor7<ImmutableSet<VariableElement>, Void>
      ENUM_ANNOTATION_VALUE_VISITOR =
          new SimpleAnnotationValueVisitor7<ImmutableSet<VariableElement>, Void>() {
            @Override
            public ImmutableSet<VariableElement> defaultAction(Object o, Void unused) {
              throw new IllegalStateException(
                  "Expected an Enum or an Enum array, got instead: " + o);
            }

            @Override
            public ImmutableSet<VariableElement> visitArray(
                List<? extends AnnotationValue> values, Void unused) {
              ImmutableSet.Builder<VariableElement> builder = ImmutableSet.builder();
              for (AnnotationValue value : values) {
                builder.addAll(value.accept(this, null));
              }
              return builder.build();
            }

            @Override
            public ImmutableSet<VariableElement> visitEnumConstant(
                VariableElement value, Void unused) {
              return ImmutableSet.of(value);
            }
          };

  /** Gets the values from an annotation value representing a Enum array. */
  public static ImmutableSet<VariableElement> getEnumArrayAnnotationValue(AnnotationValue value) {
    return value.accept(ENUM_ANNOTATION_VALUE_VISITOR, /* unused accumulator */ null);
  }

  /** Converts an annotation value map to be keyed by the attribute name. */
  public static ImmutableMap<String, AnnotationValue> convertToAttributeNameMap(
      Map<? extends ExecutableElement, ? extends AnnotationValue> annotationValues) {
    ImmutableMap.Builder<String, AnnotationValue> builder = ImmutableMap.builder();
    for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> e
        : annotationValues.entrySet()) {
      String attribute = e.getKey().getSimpleName().toString();
      builder.put(attribute, e.getValue());
    }
    return builder.build();
  }

  /** Returns the given elements containing package element. */
  public static PackageElement getPackageElement(Element originalElement) {
    checkNotNull(originalElement);
    for (Element e = originalElement; e != null; e = e.getEnclosingElement()) {
      if (e instanceof PackageElement) {
        return (PackageElement) e;
      }
    }
    throw new IllegalStateException("Cannot find a package for " + originalElement);
  }

  public static TypeElement getTopLevelType(Element originalElement) {
    checkNotNull(originalElement);
    for (Element e = originalElement; e != null; e = e.getEnclosingElement()) {
      if (isTopLevel(e)) {
        return MoreElements.asType(e);
      }
    }
    throw new IllegalStateException("Cannot find a top-level type for " + originalElement);
  }

  /** Returns true if the given element is a top-level element. */
  public static boolean isTopLevel(Element element) {
    return element.getEnclosingElement().getKind() == ElementKind.PACKAGE;
  }

  /** Returns true if the given element is annotated with the given annotation. */
  public static boolean hasAnnotation(Element element, Class<? extends Annotation> annotation) {
    return element.getAnnotation(annotation) != null;
  }

  /** Returns true if the given element has an annotation with the given class name. */
  public static boolean hasAnnotation(Element element, ClassName className) {
    return getAnnotationMirrorOptional(element, className).isPresent();
  }

  /** Returns true if the given element has an annotation with the given class name. */
  public static boolean hasAnnotation(AnnotationMirror mirror, ClassName className) {
    return hasAnnotation(mirror.getAnnotationType().asElement(), className);
  }

  /** Returns true if the given element is annotated with the given annotation. */
  public static boolean hasAnnotation(
      AnnotationMirror mirror, Class<? extends Annotation> annotation) {
    return hasAnnotation(mirror.getAnnotationType().asElement(), annotation);
  }

  /** Returns true if the given element has an annotation that is an error kind. */
  public static boolean hasErrorTypeAnnotation(Element element) {
    for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
      if (annotationMirror.getAnnotationType().getKind() == TypeKind.ERROR) {
        return true;
      }
    }
    return false;
  }


  /**
   * Returns all elements in the round that are annotated with at least 1 of the given
   * annotations.
   */
  @SafeVarargs
  public static ImmutableSet<Element> getElementsAnnotatedWith(RoundEnvironment roundEnv,
      Class<? extends Annotation>... annotations) {
    ImmutableSet.Builder<Element> builder = ImmutableSet.builder();
    for (Class<? extends Annotation> annotation : annotations){
      builder.addAll(roundEnv.getElementsAnnotatedWith(annotation));
    }

    return builder.build();
  }

  /**
   * Returns the name of a class, including prefixing with enclosing class names. i.e. for inner
   * class Foo enclosed by Bar, returns Bar_Foo instead of just Foo
   */
  public static String getEnclosedName(ClassName name) {
    return Joiner.on('_').join(name.simpleNames());
  }

  /** Returns the name of a class. See {@link #getEnclosedName(ClassName)}. */
  public static String getEnclosedName(TypeElement element) {
    return getEnclosedName(ClassName.get(element));
  }

  /**
   * Returns an equivalent class name with the {@code .} (dots) used for inner classes replaced with
   * {@code _}.
   */
  public static ClassName getEnclosedClassName(ClassName className) {
    return ClassName.get(className.packageName(), getEnclosedName(className));
  }

  /** Returns the fully qualified class name, with _ instead of . */
  public static String getFullyQualifiedEnclosedClassName(ClassName className) {
    return className.packageName().replace('.', '_') + getEnclosedName(className);
  }

  /**
   * Returns the fully qualified class name, with _ instead of . For elements that are not type
   * elements, this continues to append the simple name of elements. For example,
   * foo_bar_Outer_Inner_fooMethod.
   */
  public static String getFullEnclosedName(Element element) {
    Preconditions.checkNotNull(element);
    String qualifiedName = "";
    while (element != null) {
      if (element.getKind().equals(ElementKind.PACKAGE)) {
        qualifiedName = asPackage(element).getQualifiedName() + qualifiedName;
      } else {
        // This check is needed to keep the name stable when compiled with jdk8 vs jdk11. jdk11
        // contains newly added "module" enclosing elements of packages, which adds an addtional "_"
        // prefix to the name due to an empty module element compared with jdk8.
        if (!element.getSimpleName().toString().isEmpty()) {
          qualifiedName = "." + element.getSimpleName() + qualifiedName;
        }
      }
      element = element.getEnclosingElement();
    }
    return qualifiedName.replace('.', '_');
  }

  /** Appends the given string to the end of the class name. */
  public static ClassName append(ClassName name, String suffix) {
    return name.peerClass(name.simpleName() + suffix);
  }

  /** Prepends the given string to the beginning of the class name. */
  public static ClassName prepend(ClassName name, String prefix) {
    return name.peerClass(prefix + name.simpleName());
  }

  /**
   * Removes the string {@code suffix} from the simple name of {@code type} and returns it.
   *
   * @throws BadInputException if the simple name of {@code type} does not end with {@code suffix}
   */
  public static ClassName removeNameSuffix(TypeElement type, String suffix) {
    ClassName originalName = ClassName.get(type);
    String originalSimpleName = originalName.simpleName();
    ProcessorErrors.checkState(originalSimpleName.endsWith(suffix),
        type, "Name of type %s must end with '%s'", originalName, suffix);
    String withoutSuffix =
        originalSimpleName.substring(0, originalSimpleName.length() - suffix.length());
    return originalName.peerClass(withoutSuffix);
  }

  /** @see #getAnnotationMirror(Element, ClassName) */
  public static AnnotationMirror getAnnotationMirror(
      Element element, Class<? extends Annotation> annotationClass) {
    return getAnnotationMirror(element, ClassName.get(annotationClass));
  }

  /** @see #getAnnotationMirror(Element, ClassName) */
  public static AnnotationMirror getAnnotationMirror(Element element, String annotationClassName) {
    return getAnnotationMirror(element, ClassName.bestGuess(annotationClassName));
  }

  /**
   * Returns the annotation mirror from the given element that corresponds to the given class.
   *
   * @throws IllegalStateException if the given element isn't annotated with that annotation.
   */
  public static AnnotationMirror getAnnotationMirror(Element element, ClassName className) {
    Optional<AnnotationMirror> annotationMirror = getAnnotationMirrorOptional(element, className);
    if (annotationMirror.isPresent()) {
      return annotationMirror.get();
    } else {
      throw new IllegalStateException(
          String.format(
              "Couldn't find annotation %s on element %s. Found annotations: %s",
              className, element.getSimpleName(), element.getAnnotationMirrors()));
    }
  }

  /**
   * Returns the annotation mirror from the given element that corresponds to the given class.
   *
   * @throws {@link IllegalArgumentException} if 2 or more annotations are found.
   * @return {@link Optional#empty()} if no annotation is found on the element.
   */
  static Optional<AnnotationMirror> getAnnotationMirrorOptional(
      Element element, ClassName className) {
    return element.getAnnotationMirrors().stream()
        .filter(mirror -> ClassName.get(mirror.getAnnotationType()).equals(className))
        .collect(toOptional());
  }

  /** @return true if element inherits directly or indirectly from the className */
  public static boolean isAssignableFrom(TypeElement element, ClassName className) {
    return isAssignableFromAnyOf(element, ImmutableSet.of(className));
  }

  /** @return true if element inherits directly or indirectly from any of the classNames */
  public static boolean isAssignableFromAnyOf(TypeElement element,
      ImmutableSet<ClassName> classNames) {
    for (ClassName className : classNames) {
      if (ClassName.get(element).equals(className)) {
        return true;
      }
    }

    TypeMirror superClass = element.getSuperclass();
    // None type is returned if this is an interface or Object
    // Error type is returned for classes that are generated by this processor
    if ((superClass.getKind() != TypeKind.NONE) && (superClass.getKind() != TypeKind.ERROR)) {
      Preconditions.checkState(superClass.getKind() == TypeKind.DECLARED);
      if (isAssignableFromAnyOf(MoreTypes.asTypeElement(superClass), classNames)) {
        return true;
      }
    }

    for (TypeMirror iface : element.getInterfaces()) {
      // Skip errors and keep looking. This is especially needed for classes generated by this
      // processor.
      if (iface.getKind() == TypeKind.ERROR) {
        continue;
      }
      Preconditions.checkState(iface.getKind() == TypeKind.DECLARED,
          "Interface type is %s", iface.getKind());
      if (isAssignableFromAnyOf(MoreTypes.asTypeElement(iface), classNames)) {
        return true;
      }
    }

    return false;
  }

  /** Returns methods from a given TypeElement, not including constructors. */
  public static ImmutableList<ExecutableElement> getMethods(TypeElement element) {
    ImmutableList.Builder<ExecutableElement> builder = ImmutableList.builder();
    for (Element e : element.getEnclosedElements()) {
      // Only look for executable elements, not fields, etc
      if (e instanceof ExecutableElement) {
        ExecutableElement method = (ExecutableElement) e;
        if (!method.getSimpleName().contentEquals(CONSTRUCTOR_NAME)
            && !method.getSimpleName().contentEquals(STATIC_INITIALIZER_NAME)) {
          builder.add(method);
        }
      }
    }
    return builder.build();
  }

  public static ImmutableList<ExecutableElement> getConstructors(TypeElement element) {
    ImmutableList.Builder<ExecutableElement> builder = ImmutableList.builder();
    for (Element enclosed : element.getEnclosedElements()) {
      // Only look for executable elements, not fields, etc
      if (enclosed instanceof ExecutableElement) {
        ExecutableElement method = (ExecutableElement) enclosed;
        if (method.getSimpleName().contentEquals(CONSTRUCTOR_NAME)) {
          builder.add(method);
        }
      }
    }
    return builder.build();
  }

  /**
   * Returns all transitive methods from a given TypeElement, not including constructors. Also does
   * not include methods from Object or that override methods on Object.
   */
  public static ImmutableList<ExecutableElement> getAllMethods(TypeElement element) {
    ImmutableList.Builder<ExecutableElement> builder = ImmutableList.builder();
    builder.addAll(
        Iterables.filter(
            getMethods(element),
            method -> {
              return !isObjectMethod(method);
            }));
    TypeMirror superclass = element.getSuperclass();
    if (superclass.getKind() != TypeKind.NONE) {
      TypeElement superclassElement = MoreTypes.asTypeElement(superclass);
      builder.addAll(getAllMethods(superclassElement));
    }
    for (TypeMirror iface : element.getInterfaces()) {
      builder.addAll(getAllMethods(MoreTypes.asTypeElement(iface)));
    }
    return builder.build();
  }

  /** Checks that the given element is not the error type. */
  public static void checkForCompilationError(TypeElement e) {
    ProcessorErrors.checkState(e.asType().getKind() != TypeKind.ERROR, e,
        "Unable to resolve the type %s. Look for compilation errors above related to this type.",
        e);
  }

  private static void addInterfaceMethods(
      TypeElement type, ImmutableList.Builder<ExecutableElement> interfaceMethods) {
    for (TypeMirror interfaceMirror : type.getInterfaces()) {
      TypeElement interfaceElement = MoreTypes.asTypeElement(interfaceMirror);
      interfaceMethods.addAll(getMethods(interfaceElement));
      addInterfaceMethods(interfaceElement, interfaceMethods);
    }
  }

  /**
   * Finds methods of interfaces implemented by {@code type}. This method also checks the
   * superinterfaces of those interfaces. This method does not check the interfaces of any
   * superclass of {@code type}.
   */
  public static ImmutableList<ExecutableElement> methodsOnInterfaces(TypeElement type) {
    ImmutableList.Builder<ExecutableElement> interfaceMethods = new ImmutableList.Builder<>();
    addInterfaceMethods(type, interfaceMethods);
    return interfaceMethods.build();
  }

  /** Returns MapKey annotated annotations found on an element. */
  public static ImmutableList<AnnotationMirror> getMapKeyAnnotations(Element element) {
    return getAnnotationsAnnotatedWith(element, ClassName.get("dagger", "MapKey"));
  }

  /** Returns Qualifier annotated annotations found on an element. */
  public static ImmutableList<AnnotationMirror> getQualifierAnnotations(Element element) {
    // TODO(bcorso): Consolidate this logic with InjectionAnnotations in Dagger
    ImmutableSet<? extends AnnotationMirror> qualifiers =
        AnnotationMirrors.getAnnotatedAnnotations(element, Qualifier.class);
    KotlinMetadataUtil metadataUtil = KotlinMetadataUtils.getMetadataUtil();
    if (element.getKind() == ElementKind.FIELD
        // static fields are generally not supported, no need to get qualifier from kotlin metadata
        && !element.getModifiers().contains(STATIC)
        && metadataUtil.hasMetadata(element)) {
      VariableElement fieldElement = asVariable(element);
      return Stream.concat(
              qualifiers.stream(),
              metadataUtil.isMissingSyntheticPropertyForAnnotations(fieldElement)
                  ? Stream.empty()
                  : metadataUtil
                      .getSyntheticPropertyAnnotations(fieldElement, Qualifier.class)
                      .stream())
          .map(AnnotationMirrors.equivalence()::wrap)
          .distinct()
          .map(Wrapper::get)
          .collect(DaggerStreams.toImmutableList());
    } else {
      return ImmutableList.copyOf(qualifiers);
    }
  }

  /** Returns Scope annotated annotations found on an element. */
  public static ImmutableList<AnnotationMirror> getScopeAnnotations(Element element) {
    return getAnnotationsAnnotatedWith(element, ClassNames.SCOPE);
  }

  /** Returns annotations of element that are annotated with subAnnotation */
  public static ImmutableList<AnnotationMirror> getAnnotationsAnnotatedWith(
      Element element, ClassName subAnnotation) {
    ImmutableList.Builder<AnnotationMirror> builder = ImmutableList.builder();
    element.getAnnotationMirrors().stream()
        .filter(annotation -> hasAnnotation(annotation, subAnnotation))
        .forEach(builder::add);
    return builder.build();
  }

  /** Returns true if there are any annotations of element that are annotated with subAnnotation */
  public static boolean hasAnnotationsAnnotatedWith(Element element, ClassName subAnnotation) {
    return !getAnnotationsAnnotatedWith(element, subAnnotation).isEmpty();
  }

  /**
   * Returns true iff the given {@code method} is one of the public or protected methods on {@link
   * Object}, or an overridden version thereof.
   *
   * <p>This method ignores the return type of the given method, but this is generally fine since
   * two methods which only differ by their return type will cause a compiler error. (e.g. a
   * non-static method with the signature {@code int equals(Object)})
   */
  public static boolean isObjectMethod(ExecutableElement method) {
    // First check if this method is directly defined on Object
    Element enclosingElement = method.getEnclosingElement();
    if (enclosingElement.getKind() == ElementKind.CLASS
        && TypeName.get(enclosingElement.asType()).equals(TypeName.OBJECT)) {
      return true;
    }

    if (method.getModifiers().contains(Modifier.STATIC)) {
      return false;
    }
    switch (method.getSimpleName().toString()) {
      case "equals":
        return (method.getParameters().size() == 1)
            && (method.getParameters().get(0).asType().toString().equals("java.lang.Object"));
      case "hashCode":
      case "toString":
      case "clone":
      case "getClass":
      case "notify":
      case "notifyAll":
      case "finalize":
        return method.getParameters().isEmpty();
      case "wait":
        if (method.getParameters().isEmpty()) {
          return true;
        } else if ((method.getParameters().size() == 1)
            && (method.getParameters().get(0).asType().toString().equals("long"))) {
          return true;
        } else if ((method.getParameters().size() == 2)
            && (method.getParameters().get(0).asType().toString().equals("long"))
            && (method.getParameters().get(1).asType().toString().equals("int"))) {
          return true;
        }
        return false;
      default:
        return false;
    }
  }

  public static ParameterSpec parameterSpecFromVariableElement(VariableElement element) {
    return ParameterSpec.builder(
        ClassName.get(element.asType()),
        upperToLowerCamel(element.getSimpleName().toString()))
        .build();
  }

  /**
   * Shortcut for converting from upper camel to lower camel case
   *
   * <p>Example: "SomeString" => "someString"
   */
  public static String upperToLowerCamel(String upperCamel) {
    return CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, upperCamel);
  }

  /** @return copy of the given MethodSpec as {@link MethodSpec.Builder} with method body removed */
  public static MethodSpec.Builder copyMethodSpecWithoutBody(MethodSpec methodSpec) {
    MethodSpec.Builder builder;

    if (methodSpec.isConstructor()) {
      // Constructors cannot have return types
      builder = MethodSpec.constructorBuilder();
    } else {
      builder = MethodSpec.methodBuilder(methodSpec.name)
          .returns(methodSpec.returnType);
    }

    return builder
        .addAnnotations(methodSpec.annotations)
        .addModifiers(methodSpec.modifiers)
        .addParameters(methodSpec.parameters)
        .addExceptions(methodSpec.exceptions)
        .addJavadoc(methodSpec.javadoc.toString())
        .addTypeVariables(methodSpec.typeVariables);
  }

  /** @return A method spec for an empty constructor (useful for abstract Dagger modules). */
  public static MethodSpec privateEmptyConstructor() {
    return MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build();
  }

  /**
   * Returns true if the given method is annotated with one of the annotations Dagger recognizes
   * for abstract methods (e.g. @Binds).
   */
  public static boolean hasDaggerAbstractMethodAnnotation(ExecutableElement method) {
    return hasAnnotation(method, ClassNames.BINDS)
        || hasAnnotation(method, ClassNames.BINDS_OPTIONAL_OF)
        || hasAnnotation(method, ClassNames.MULTIBINDS)
        || hasAnnotation(method, ClassNames.CONTRIBUTES_ANDROID_INJECTOR);
  }

  public static ImmutableSet<TypeElement> toTypeElements(Elements elements, String[] classes) {
    return FluentIterable.from(classes).transform(elements::getTypeElement).toSet();
  }

  public static ImmutableSet<ClassName> toClassNames(Iterable<TypeElement> elements) {
    return FluentIterable.from(elements).transform(ClassName::get).toSet();
  }

  public static boolean requiresModuleInstance(Elements elements, TypeElement module) {
    // Binding methods that lack ABSTRACT or STATIC require module instantiation.
    // Required by Dagger.  See b/31489617.
    return ElementFilter.methodsIn(elements.getAllMembers(module)).stream()
        .filter(Processors::isBindingMethod)
        .map(ExecutableElement::getModifiers)
        .anyMatch(modifiers -> !modifiers.contains(ABSTRACT) && !modifiers.contains(STATIC));
  }

  private static boolean isBindingMethod(ExecutableElement method) {
    return hasAnnotation(method, ClassNames.PROVIDES)
        || hasAnnotation(method, ClassNames.BINDS)
        || hasAnnotation(method, ClassNames.BINDS_OPTIONAL_OF)
        || hasAnnotation(method, ClassNames.MULTIBINDS);
  }

  public static void addGeneratedAnnotation(
      TypeSpec.Builder typeSpecBuilder, ProcessingEnvironment env, Class<?> generatorClass) {
    addGeneratedAnnotation(typeSpecBuilder, env, generatorClass.getName());
  }

  public static void addGeneratedAnnotation(
      TypeSpec.Builder typeSpecBuilder, ProcessingEnvironment env, String generatorClass) {
    GeneratedAnnotations.generatedAnnotation(env.getElementUtils(), env.getSourceVersion())
        .ifPresent(
            annotation ->
                typeSpecBuilder.addAnnotation(
                    AnnotationSpec.builder(ClassName.get(annotation))
                        .addMember("value", "$S", generatorClass)
                        .build()));
  }

  public static AnnotationSpec getOriginatingElementAnnotation(TypeElement element) {
    TypeName rawType = rawTypeName(ClassName.get(getTopLevelType(element)));
    return AnnotationSpec.builder(ClassNames.ORIGINATING_ELEMENT)
        .addMember("topLevelClass", "$T.class", rawType)
        .build();
  }

  /**
   * Returns the {@link TypeName} for the raw type of the given type name. If the argument isn't a
   * parameterized type, it returns the argument unchanged.
   */
  public static TypeName rawTypeName(TypeName typeName) {
    return (typeName instanceof ParameterizedTypeName)
        ? ((ParameterizedTypeName) typeName).rawType
        : typeName;
  }

  private Processors() {}
}
